package com.ikingtech.framework.sdk.plugin.core;

import com.ikingtech.framework.sdk.context.exception.FrameworkException;
import com.ikingtech.framework.sdk.enums.plugin.PluginTypeEnum;
import com.ikingtech.framework.sdk.utils.Tools;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.core.MethodIntrospector;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.util.ClassUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author tie yan
 */
@Slf4j
public class CrudPluginRegistrar extends AbstractPluginRegistrar {

    private final Map<String, List<RequestMappingInfo>> requestMappingMap = new ConcurrentHashMap<>();

    private final Map<String, String> mapperScannerMap = new ConcurrentHashMap<>();

    @Override
    public void register(PluginRegistrarArgs args) {
        Map<String, PluggableClass> classMap = Tools.Coll.convertMap(args.getClasses(), PluggableClass::getName);

        this.loadJarClass(args.getJarFileName(), args.getClasses());
        if (Tools.Coll.isNotBlankMap(this.loadedClassMap)) {
            this.loadedClassMap.get(args.getJarFileName()).forEach((className, clazz) -> {
                if (Boolean.TRUE.equals(classMap.get(className).getBean())) {
                    this.registerBean(args.getJarFileName(), className, clazz);
                }
            });
            this.loadedClassMap.get(args.getJarFileName()).forEach((className, clazz) -> {
                if (Boolean.TRUE.equals(classMap.get(className).getController())) {
                    this.registerController(args.getJarFileName(), className);
                }
            });
        }
    }

    @Override
    public void unregister(PluginRegistrarArgs args) {
        this.unloadJarClass(args.getJarFileName());
        this.unregisterMapperScanConfigure(args.getJarFileName());
        this.unregisterBean(args.getJarFileName());
        this.unregisterController(args.getJarFileName());
        this.loadedClassMap.remove(args.getJarFileName());

    }

    @Override
    public PluginTypeEnum type() {
        return PluginTypeEnum.CRUD;
    }

    public void registerController(String jarFileName, String beanName) {
        final RequestMappingHandlerMapping requestMappingHandlerMapping = (RequestMappingHandlerMapping) this.applicationContext.getBean("requestMappingHandlerMapping");
        Class<?> beanType = this.applicationContext.getType(beanName);
        if (null == beanType) {
            throw new FrameworkException("获取bean[{}] class失败", beanName);
        }
        Class<?> beanClassType = ClassUtils.getUserClass(beanType);
        Map<Method, RequestMappingInfo> methods = MethodIntrospector.selectMethods(beanClassType,
                (MethodIntrospector.MetadataLookup<RequestMappingInfo>) method -> {
                    try {
                        RequestMappingInfo info = this.createRequestMappingInfo(method);
                        if (null != info) {
                            RequestMappingInfo typeInfo = createRequestMappingInfo(beanClassType);
                            if (null != typeInfo) {
                                info = typeInfo.combine(info);
                            }
                        }
                        return info;
                    } catch (Throwable ex) {
                        throw new IllegalStateException("Invalid mapping on handler class [" + beanClassType.getName() + "]: " + method, ex);
                    }
                });
        List<RequestMappingInfo> requestMappingInfos = new ArrayList<>();
        methods.forEach((method, mapping) -> {
            Method invocableMethod = AopUtils.selectInvocableMethod(method, beanClassType);
            requestMappingHandlerMapping.registerMapping(mapping, beanName, invocableMethod);
            requestMappingInfos.add(mapping);
        });
        this.requestMappingMap.computeIfAbsent(jarFileName, k -> new ArrayList<>()).addAll(requestMappingInfos);
    }

    public void unregisterController(String jarFileName) {
        final RequestMappingHandlerMapping requestMappingHandlerMapping = this.applicationContext.getBean("requestMappingHandlerMapping", RequestMappingHandlerMapping.class);
        this.requestMappingMap.get(jarFileName).forEach(requestMappingHandlerMapping::unregisterMapping);
        this.requestMappingMap.remove(jarFileName);
    }

    public void unregisterMapperScanConfigure(String jarFileName) {
        Map<String, Class<?>> classMap = this.loadedClassMap.get(jarFileName);
        for (String className : classMap.keySet()) {
            if (className.endsWith("Mapper")) {
                this.beanFactory.removeBeanDefinition(Tools.Str.toLowerCamelCaseWithoutSeparate(className.substring(className.lastIndexOf(".") + 1)));
            }
        }
        this.beanFactory.removeBeanDefinition(this.mapperScannerMap.get(jarFileName));
    }

    private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
        RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
        if (null == requestMapping) {
            return null;
        }
        return RequestMappingInfo
                .paths(requestMapping.path())
                .methods(requestMapping.method())
                .params(requestMapping.params())
                .headers(requestMapping.headers())
                .consumes(requestMapping.consumes())
                .produces(requestMapping.produces())
                .mappingName(requestMapping.name()).build();
    }
}
